package owg.util.editor2d;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsEnvironment;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.ArrayList;

import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JToggleButton;

import owg.util.ClassFinder;
import owg.util.awt.ExtensionFilter;
import owg.util.awt.StatefulJFrame;
import owg.util.data.property.PropertyImmutable;
import owg.util.data.property.PropertyListener;
import owg.util.editor2d.entity.Placeable2D;


public class Editor2DUI {
	public final static byte WINDOW_OPEN = 0;
	public final static byte WINDOW_CLOSING = 1;
	public final static byte WINDOW_CLOSED = 2;
	public final static int LOG_LENGTH = 3;
	public final Editor2D editor;
	public final StatefulJFrame editorFrame;
	
	private byte windowState = WINDOW_CLOSED;
	
	private ArrayList<Class<? extends Placeable2D>> availableClasses;
	protected String[] classNames;
	protected Class<? extends Placeable2D> currentClass;
	
	private JPanel editorTLP;
	private JComponent topLabel;
	private JLabel[] logLabels;
	private String[] logStrings = new String[LOG_LENGTH];
	
	private JComboBox<String> classBox;
	private boolean ignoreClassBoxEvents = false;
	private ClassUI classUI;
	
	private ObjectParamsPane paramsPane;
	private Editor2DMenuBar menuBar;
	
	private JToggleButton createButton, destroyButton;
	private PropertyListener<Boolean> createListener, destroyListener;
	
	private MiscConfigPane miscConfig;
	
	
	public Editor2DUI(Editor2D editor)
	{
		this.editor = editor;
		editorFrame = new StatefulJFrame("Dweeb2D");
		editorFrame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
		editorFrame.addWindowListener(new WindowAdapter()
		{
			@Override
			public void windowClosing(WindowEvent e)
			{
				windowState = WINDOW_CLOSING;
			}
			
			@Override
			public void windowClosed(WindowEvent e)
			{
				windowState = WINDOW_CLOSED;
			}
			@Override
			public void windowOpened(WindowEvent e)
			{
				windowState = WINDOW_OPEN;
			}
		});
		editorFrame.addComponentListener(new ComponentAdapter(){
			public void componentHidden(ComponentEvent e){}
			public void componentShown(ComponentEvent e){}});
		
		menuBar = new Editor2DMenuBar(this);
		editorFrame.setJMenuBar(menuBar);
		editorTLP = new JPanel(new GridBagLayout());
		editorFrame.add(editorTLP);
	}
	
	public void rebuild()
	{
		clean();
		GridBagConstraints c = new GridBagConstraints();
		c.fill = GridBagConstraints.HORIZONTAL;
		c.anchor = GridBagConstraints.NORTH;
		c.gridwidth = 1;
		c.gridheight = 1;
		c.gridx = 0;
		c.gridy = GridBagConstraints.RELATIVE;
		c.weightx = 1;
		c.weighty = 0;
		//Get all texture strings TODO this stuff
		/*textureStrings = GFX.getAllTextureNames(false);
	objStrings = Calc.filter(new File("res/models").list(), "(.*\\.obj)");
	Arrays.sort(objStrings);
	bvhStrings = Calc.filter(new File("res/armature").list(), "(.*\\.bvh)");
	Arrays.sort(bvhStrings);*/
		
		//Add label to hold initial key focus
		topLabel = new JPanel() {
			private static final long serialVersionUID = -2953600943601770937L;
			@Override
			protected void paintComponent(Graphics g)
			{
				g.setColor(Color.LIGHT_GRAY);
				for(int x = 0; x<getWidth(); x += 40)
				{
					g.drawString("dweeb", x, 22);
				}
				if(g instanceof Graphics2D)
				{
					Graphics2D g2 = (Graphics2D) g;
					g2.translate(getWidth(), 2);
					g2.rotate(Math.PI);
					for(int x = 0; x<getWidth(); x += 40)
					{
						g.drawString("dweeb", x, 0);
					}
				}
				else
					System.err.println("Warning: Swing implementation does not support Graphics2D...");
			}
		};
		topLabel.setFocusable(true);
		//Allow catching tab key events on this component
		topLabel.setFocusTraversalKeysEnabled(false);
		int size = 24;
		c.gridy = 0;
		//For some fucked up reason the layout will not respect topLabel's preferredSize, so add an overlapping filler.
		editorTLP.add(new Box.Filler(new Dimension(1,size),new Dimension(1,size),new Dimension(1,size)),c);
		c.fill = GridBagConstraints.BOTH;
		editorTLP.add(topLabel, c);
		c.fill = GridBagConstraints.HORIZONTAL;
		c.gridy = GridBagConstraints.RELATIVE;
		
		if(availableClasses == null)
		{
			//Get all placeable classes
			availableClasses = new ArrayList<>();
			for(String packageName : editor.packages)
				availableClasses.addAll(ClassFinder.getSubclassesIn(new File(editor.workingDir, "editorSettings"), packageName, true, false, Placeable2D.class));
			currentClass = availableClasses.get(0);
		}
		//Create a list of the simple class names and find the currently selected class in the list
		classNames = new String[availableClasses.size()];
		int currentClassIndex = -1;
		for(int i=0; i<classNames.length; i++)
		{
			classNames[i] = availableClasses.get(i).getSimpleName();
			if(availableClasses.get(i).equals(currentClass))
				currentClassIndex = i;
		}
		if(currentClassIndex == -1) throw new RuntimeException("Class selected in editor no longer exists!?");
		
		//Make the classes selectable in a JComboBox
		classBox = new JComboBox<String>(classNames);
		classBox.setSelectedIndex(currentClassIndex);
		classBox.addActionListener(new ActionListener(){public void actionPerformed(ActionEvent e){changeClass(classBox.getSelectedIndex(), false);}});
		classBox.setBorder(BorderFactory.createTitledBorder("Object Class"));
		editorTLP.add(classBox, c);
		
		classUI = new ClassUI(this, currentClass);
		editorTLP.add(classUI, c);
		
		editorTLP.add(makePaintPanel(), c);
		paramsPane = new ObjectParamsPane(this);
		c.weighty = 1;
		c.fill = GridBagConstraints.BOTH;
		editorTLP.add(paramsPane, c);
		
		//Add a small spacer
		c.weighty = 0;
		editorTLP.add(new Box.Filler(new Dimension(1,8),new Dimension(1,8),new Dimension(1,8)),c);
		c.weighty = 0;
		//Rest is on bottom
		
		miscConfig = new MiscConfigPane(this);
		editorTLP.add(miscConfig, c);
		
		logLabels = new JLabel[logStrings.length];
		for(int i = 0; i<logStrings.length; i++)
		{
			JLabel l = new JLabel(" ");
			if(logStrings[i] != null)
				l.setText(logStrings[i]);
			l.setOpaque(true);
			l.setBackground(Color.BLACK);
			l.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 10));
			
			l.setForeground(new Color(0,1f-(float)i/logStrings.length,0));
			logLabels[i] = l;
			JPanel stretcher = new JPanel();
			stretcher.setLayout(new BorderLayout());
			stretcher.add(l,BorderLayout.CENTER);
			editorTLP.add(stretcher, c);
		}
		if (editor.settings.windowedBounds == null || editor.settings.windowedBounds.isEmpty())
		{
			editorFrame.pack();
			Rectangle bounds = GraphicsEnvironment.getLocalGraphicsEnvironment().getMaximumWindowBounds();
			editorFrame.setSize(editorFrame.getWidth(), bounds.height);
			editorFrame.setLocation(bounds.x+bounds.width-editorFrame.getWidth(), bounds.y);
		}
		else
			editorFrame.setBounds(editor.settings.windowedBounds);
		if(editor.settings.isFullscreen)
			editorFrame.setExtendedState(Frame.MAXIMIZED_BOTH);
	}
	private Component makePaintPanel() {
		JPanel p = new JPanel();
		p.setBorder(BorderFactory.createTitledBorder("Paint Mode"));
		p.setLayout(new BoxLayout(p, BoxLayout.X_AXIS));
		
		createButton = new JToggleButton("LMB create[shift-space]", editor.settings.createPaint.get());
		createButton.setToolTipText("When enabled, create new objects by pressing the left mouse button on the level");
		createListener = new PropertyListener<Boolean>() {
			@Override
			public void valueChanged(PropertyImmutable<? extends Boolean> property,
				Boolean oldValue, Boolean newValue) {
				if((boolean)property.get() != (boolean)createButton.isSelected())
					createButton.setSelected(property.get());		
			}
		};
		editor.settings.createPaint.addListener(null, createListener);
		createButton.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				editor.settings.createPaint.set(createButton.isSelected(), createListener);
			}
		});
		p.add(createButton);
		
		destroyButton = new JToggleButton("LMB destroy[shift-X]", editor.settings.destroyPaint.get());
		destroyButton.setToolTipText("When enabled, destroy objects by pressing the left mouse button on the level");
		destroyListener = new PropertyListener<Boolean>() {
			@Override
			public void valueChanged(PropertyImmutable<? extends Boolean> property,
				Boolean oldValue, Boolean newValue) {
				if((boolean)property.get() != destroyButton.isSelected())
					destroyButton.setSelected(property.get());		
			}
		};
		editor.settings.destroyPaint.addListener(null, destroyListener);
		destroyButton.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				editor.settings.destroyPaint.set(destroyButton.isSelected(), destroyListener);
			}
		});
		p.add(destroyButton);
		
		return p;
	}
	
	public void clean()
	{
		if(classUI != null)
		{
			classUI.dumpParamsToFiles(currentClass);
			classUI.dumpTabIndexToFile(currentClass);
		}
		
		editorTLP.removeAll();
		logLabels = null;
		//textureStrings = null;
		//objStrings = null; //???
		//bvhStrings = null;
		topLabel = null;
		classBox = null;
		
		createButton = null; 
		destroyButton = null;
		createListener = null; 
		destroyListener = null;
		
		paramsPane = null;
		miscConfig = null;
		ignoreClassBoxEvents = false;
		classUI = null;
	}
	
	protected void changeClass(int selectedIndex, boolean changeBoxSelection)
	{
		if(!ignoreClassBoxEvents && !availableClasses.get(selectedIndex).equals(currentClass))
		{
			if(changeBoxSelection)
			{
				ignoreClassBoxEvents = true;
				classBox.setSelectedIndex(selectedIndex);
				ignoreClassBoxEvents = false;
			}
			classUI.dumpParamsToFiles(currentClass);
			classUI.dumpTabIndexToFile(currentClass);
			currentClass = availableClasses.get(selectedIndex);
			classUI.setClass(currentClass);
		}
	}
	
	public void setEnabled(boolean b) {
		if(b)
		{
			rebuild();
			editorFrame.setVisible(true);
			windowState = WINDOW_OPEN;
		}
		else
		{
			editorFrame.setVisible(false);
			windowState = WINDOW_CLOSED;
			clean();
		}
	}
	
	public byte getWindowState()
	{
		return windowState;
	}
	public void windowCloseCancelled()
	{
		windowState = WINDOW_OPEN;
	}
	public JFrame getFrame()
	{
		if(windowState == WINDOW_CLOSED)
			return null;
		else
			return editorFrame;
	}
	
	public void message(String message)
	{
		pushMessageAt(0, message);
	}
	public void pushMessageAt(int index, String message)
	{
		for(int i = logStrings.length-1; i>index; i--)
			logStrings[i] = logStrings[i-1];
		logStrings[index] = message;
		if(logLabels != null)
		{
			for(int i = index; i<logStrings.length; i++)
				if(logStrings[i] != null)
					logLabels[i].setText(logStrings[i]);
		}
	}
	public void replaceMessage(String newMessage)
	{
		logStrings[0] = newMessage;
		if(logLabels != null)
			logLabels[0].setText(newMessage);
	}
	
	/**The selected object(s) has changed*/
	public void selectionChanged() {
		if(paramsPane != null)
			paramsPane.maybePivotChanged();
	}
	
	public ConstructorParams getSelectedConstructorParams() {
		if(classUI == null)
			return null;
		else
			return classUI.getConstructorParams();
	}
	/**Set to true when a mode is active.*/
	public void setParamsLocked(boolean b)
	{
		menuBar.setLocked(b);
		paramsPane.setLocked(b);
	}
	/**Called when any operation completes*/
	public void operationCompleted()
	{
		paramsPane.maybePivotParamsChanged();
	}
	
	public void open()
	{
		JFileChooser fileChooser = mkFileChooser();
		while(true)
		{
			int result = fileChooser.showOpenDialog(null);
			if(result != JFileChooser.APPROVE_OPTION)
				break;
			File f = fileChooser.getSelectedFile();
			if(f == null)
			{
				JOptionPane.showMessageDialog(null, "You must select a file.", "Error", JOptionPane.ERROR_MESSAGE);
				continue;
			}
			try {
				editor.open(f);
				break;
			} catch (FileNotFoundException e)
			{
				JOptionPane.showMessageDialog(null, e.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
				continue;
			} catch (Exception e)
			{
				JOptionPane.showMessageDialog(null, e.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
				e.printStackTrace();
				break;
			}
		}
	}
	
	public boolean save(boolean saveAs)
	{
		try 
		{
			File f = null;
			if (!saveAs && editor.currentFile() != null)
				f = editor.currentFile();
			else
			{
				JFileChooser fileChooser = mkFileChooser();
				while(f == null)
				{
					int result = fileChooser.showSaveDialog(null);
					if(result != JFileChooser.APPROVE_OPTION)
						break;
					f = fileChooser.getSelectedFile();
					if(f == null)
					{
						JOptionPane.showMessageDialog(null, "You must select a file.", "Error", JOptionPane.ERROR_MESSAGE);
						continue;
					}
					if(!f.getName().endsWith(".dat"))
						f = new File(f.getParentFile(), f.getName()+".dat");
					if(f.exists())
					{
						int answer = JOptionPane.showConfirmDialog(null, f+" already exists. Overwrite it?", 
							"File already exists", JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE);
						if(answer != JOptionPane.YES_OPTION)
							f = null; //Don't use this file
						if(answer == JOptionPane.CANCEL_OPTION)
							break;//Don't ask again
					}
				}
			}
			
			if(f != null)
			{
				editor.save(f);
				return true;
			}
		} catch (Exception e)
		{
			JOptionPane.showMessageDialog(null, e.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
			e.printStackTrace();
		}
		return false;
	}

	private JFileChooser mkFileChooser()
	{
		final File dir, file;
		if(editor.currentFile() != null)
		{
			dir = editor.currentFile().getParentFile();
			file = editor.currentFile();
		}
		else
		{
			dir = editor.workingDir;
			file = null;
		}
		JFileChooser fileChooser = new JFileChooser(dir);
		fileChooser.setSelectedFile(file);
		fileChooser.setFileFilter(new ExtensionFilter(".dat"));
		return fileChooser;
	}

	public void clearMap()
	{
		int result = JOptionPane.showConfirmDialog(null, "Are you sure you want to start a new map?\nUnsaved changes will be forever lost.", 
			"Are you sure?", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE);
		if(result == JOptionPane.YES_OPTION)
			editor.clearMap();
	}
}
